#include "stdafx.h"
#include <d3d9.h>
#include <d3dx9.h>
#include <windows.h>
#include <stdio.h>
#include "GWFuncs.h"
#include "DirectX.h"

// Import functions from directX libraries
#pragma comment(lib, "d3d9.lib")
#pragma comment(lib, "d3dx9.lib")

// These are the two functions that we will hook
typedef int(__stdcall *EndScene_t)(IDirect3DDevice9* pDevice);
EndScene_t pEndScene; // Pointer to the normal directX endscene function
typedef int(__stdcall *Reset_t)(IDirect3DDevice9* pDevice, D3DPRESENT_PARAMETERS* pPresentationParameters);
Reset_t pReset; // Pointer to the normal directX reset function

LPD3DXFONT Font; // DX font so we can draw the timer text
char textstr[128]; // Global var holding the text we want to display in our timer e.g. ""
box_text textbox = { 15.0f,136.0f,85.0f,20.0f, D3DCOLOR_ARGB(0xFF,0xFF,0x00,0x00) }; // Info for our text
box_solid transbox = { 10.0f,135.0f,75.0f,20.0f, D3DCOLOR_RGBA(0x63,0x63,0x63,0x88) }; // Info for the transparent box around the text
float boxwidth = 75.0f; // this is seperate because if we have 1:54:20 or 100:54:20 then the box is a different width
float indentx = 5.0f; // indentation of the text from the transparent box
float indenty = 1.0f; // indentation of the text from the transparent box
char boxfont[50];
byte fontsize = 24;
float textcharindent = 10.0;

//------------------------------------------------------------------------------
// Global vars in dllmain.cpp
extern char inifile[MAX_PATH]; // Stores the path to the speed.ini file. This string is loaded on startup of the dll
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
// Functions for DX hook
void* DetourFunc(BYTE  *src, const BYTE  *dst, const int len) // the same as the writejmp function above, but we use this for the directX hook
{                                                             // this function also stores the overwritten code so we can run it later
	BYTE  *jmp = (BYTE* )malloc(len+5); // Create len+5 bytes of memory somewhere
	DWORD dwback;
	VirtualProtect(jmp, len+5, PAGE_EXECUTE_READWRITE, &dwback); //This is the addition needed for Windows 7 RC
	VirtualProtect(src, len, PAGE_READWRITE, &dwback);
	memcpy(jmp, src, len); // jmp points to our len+5 bytes of memory we just created. So here we save the code we are about to overwrite
	jmp += len; // so jmp now points to just after the saved code
	jmp[0] = 0xE9; // opcode for a relative jmp instruction
	*(DWORD* )(jmp+1) = (DWORD)(src+len - jmp) - 5; // so the code at address jmp is now _asm { JMP src } so we have: <saved code> JMP src
	src[0] = 0xE9; // now we overwrite the code at src with our own jump                                                          ^---jmp points here at this time
	*(DWORD* )(src+1) = (DWORD)(dst - src) - 5; // at address src is now _asm { JMP dst }
	VirtualProtect(src, len, dwback, &dwback); // restore permissions
	return (jmp-len); // return the address that jmp pointed to orginally. So the return value points to: <saved code> JMP src
}

bool bDataCompare(const BYTE* pData, const BYTE* bMask, const char* szMask) // used in the vtable lookup
{
	for(;*szMask;++szMask,++pData,++bMask)
		if(*szMask=='x' && *pData!=*bMask )
			return false;
	return ((*szMask) == NULL);
}

DWORD dwFindPattern(DWORD dwAddress,DWORD dwLen,BYTE *bMask,char * szMask) // used in the vtable lookup
{
	for(DWORD i=0; i < dwLen; i++)
		if( bDataCompare( (BYTE*)( dwAddress+i ),bMask,szMask) )
			return (DWORD)(dwAddress+i);
	return 0;
}

// This is our function to draw the transparent box
void DrawRectangleAlpha(IDirect3DDevice9* pDevice, float x, float y, float width, float height, DWORD color)
{
        struct Vertex
        {
                float x,y,z,rhw;
                DWORD color;
        };
        Vertex qV[4] = { {x, y+height, 0.0f, 0.0f, color},
                         {x, y, 0.0f, 0.0f, color},
                         {x+width,y+height, 0.0f, 0.0f, color},
                         {x+width,y, 0.0f, 0.0f, color} };

        pDevice->SetRenderState(D3DRS_ALPHABLENDENABLE,1);
        pDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
        pDevice->SetRenderState(D3DRS_ZENABLE, D3DZB_FALSE);
        pDevice->SetFVF(D3DFVF_XYZRHW | D3DFVF_DIFFUSE);
        pDevice->DrawPrimitiveUP(D3DPT_TRIANGLESTRIP,2,qV,sizeof(Vertex));
}

// Our endscene hook. What happens is, the start of the original DX endscene() is overwritten with a jump to this function, so we call the actual EndScene() at the end of this function
HRESULT __stdcall hkEndScene(IDirect3DDevice9* pDevice)
{
	IDirect3DStateBlock9* stateBlock = NULL;
	pDevice->CreateStateBlock(D3DSBT_ALL, &stateBlock); // Stateblock saves all the rendersettings, as we are gonig to modify them in order to draw our stuff

	if (Font == NULL) { // initialize the font ( so this is only run the first time or if our font is destroyed somehow)
		D3DXCreateFontA(pDevice,fontsize,0,FW_BOLD,0,false,DEFAULT_CHARSET,OUT_DEFAULT_PRECIS,DEFAULT_QUALITY,DEFAULT_PITCH | FF_DONTCARE, (LPCSTR) &boxfont, &Font);
	}
    DrawRectangleAlpha(pDevice,transbox.x,transbox.y,transbox.w,transbox.h,transbox.color); // draw the transparent box
	
	RECT r = {textbox.x,textbox.y,(textbox.x+textbox.w),(textbox.y+textbox.h)}; // upperleft_x, upperleft_y, lowerright_x, lowerright_y
    Font->DrawTextA(NULL, (LPCSTR) &textstr, -1, (LPRECT) &r, DT_LEFT, textbox.color); // draw the text

	HRESULT tempreturnval = pEndScene(pDevice); // call the original DX EndScene()

	stateBlock->Apply(); // restore the renderstates now we are done drawing
	stateBlock->Release(); // release memory for the stateblock we used
	return tempreturnval;
}

// hook the reset function
HRESULT __stdcall hkReset(IDirect3DDevice9* pDevice, D3DPRESENT_PARAMETERS* pPresentationParameters)
{
	if (Font) Font->OnLostDevice(); // standard checks for directX stuff
	HRESULT returnval = pReset(pDevice, pPresentationParameters); // call the original DX Reset()
	if ((returnval == D3D_OK) && Font) Font->OnResetDevice();
    return returnval;
}

void D3DInit() // Hooks the DirectX functions
{
	HMODULE hD3D9 = NULL;
	while(!hD3D9)
	{
		hD3D9 = GetModuleHandleA("d3d9.dll");
		Sleep(5);
	}
	DWORD* VTableStart = 0;
	DWORD pA1 = dwFindPattern((DWORD)hD3D9, 0x128000, (PBYTE)"\xC7\x06\x00\x00\x00\x00\x89\x86\x00\x00\x00\x00\x89\x86", "xx????xx????xx");
	memcpy(&VTableStart, (void*)(pA1+2), 4);
	DWORD dwEndScene = VTableStart[42];
	DWORD dwReset = VTableStart[16];
	pEndScene = (EndScene_t)DetourFunc((BYTE*)dwEndScene, (BYTE*)hkEndScene, 5);
	pReset = (Reset_t)DetourFunc((BYTE*)dwReset, (BYTE*)hkReset, 5);
}

void DXSettingsInit()
{
	char tempstr[50];

	// Floating box settings
	GetPrivateProfileStringA("box","x",NULL,tempstr,50,inifile);
	sscanf_s(tempstr,"%f",&transbox.x);

	GetPrivateProfileStringA("box","y",NULL,tempstr,50,inifile);
	sscanf_s(tempstr,"%f",&transbox.y);

	GetPrivateProfileStringA("box","w",NULL,tempstr,50,inifile);
	sscanf_s(tempstr,"%f",&boxwidth);
	transbox.w = boxwidth;
	textbox.w = boxwidth + 15.0f;

	GetPrivateProfileStringA("box","h",NULL,tempstr,50,inifile);
	sscanf_s(tempstr,"%f",&transbox.h);
	textbox.h = transbox.h;

	GetPrivateProfileStringA("box","color",NULL,tempstr,50,inifile);
	sscanf_s(tempstr,"0x%x",&transbox.color);

	// Font settings
	GetPrivateProfileStringA("font","indentx",NULL,tempstr,50,inifile);
	sscanf_s(tempstr,"%f",&indentx);
	textbox.x = transbox.x + indentx;
	GetPrivateProfileStringA("font","indenty",NULL,tempstr,50,inifile);
	sscanf_s(tempstr,"%f",&indenty);
	textbox.y = transbox.y + indenty;

	GetPrivateProfileStringA("font","font",NULL,tempstr,50,inifile);
	memcpy(&boxfont,&tempstr,50);
	GetPrivateProfileStringA("font","color",NULL,tempstr,50,inifile);
	sscanf_s(tempstr,"0x%x",&textbox.color);
	GetPrivateProfileStringA("font","size",NULL,tempstr,50,inifile);
	sscanf_s(tempstr,"%u",&fontsize);
	GetPrivateProfileStringA("font","extraspace",NULL,tempstr,50,inifile);
	sscanf_s(tempstr,"%f",&textcharindent);
}

//------------------------------------------------------------------------------

BOOL CALLBACK EnumWindowProc(HWND hwnd, LPARAM lParam)
{
        if (!IsWindowVisible(hwnd)) {
                return true;
        }
        if (GetParent(hwnd) != NULL) {
                return true;
        }
        HWND Owner = GetWindow(hwnd,GW_OWNER);
        long ExStyle = GetWindowLong(hwnd,GWL_EXSTYLE);
        if ( (Owner != NULL) && ((ExStyle & WS_EX_APPWINDOW) == 0) ) {
                return true;
        }
        if ((ExStyle & WS_EX_TOOLWINDOW) > 0) {
                return true;
        }
		DWORD pid;
		if (GetWindowThreadProcessId(hwnd,&pid)) {
			if (pid == GetCurrentProcessId()) {
				memcpy((void*)lParam,&hwnd,sizeof(HWND));
				return false;
			}
		}
        return true;
}

void UpdateText(long time)
{
	long temp = time;
    if (temp != 0) {
		long hour,min,sec; //,msec;
		hour = temp/3600000;
		temp = temp%3600000;
		min = temp/60000;
		temp = temp%60000;
		sec = temp/1000;
		//temp = temp%1000;
		//msec = temp;
		if (hour >= 100) {
			transbox.w = boxwidth + textcharindent + textcharindent; // change the box width if the text is going to be long
		} else {
			if (hour >= 10) {
				transbox.w = boxwidth + textcharindent;
			} else {
				transbox.w = boxwidth;
			}
		}
		if (min < 10) {
			if (sec < 10) {
				sprintf_s(textstr, "%lu:0%lu:0%lu",hour,min,sec);
			} else {
				sprintf_s(textstr, "%lu:0%lu:%lu",hour,min,sec);
			}
		} else {
			if (sec < 10) {
				sprintf_s(textstr, "%lu:%lu:0%lu",hour,min,sec);
            }
            else {
				sprintf_s(textstr, "%lu:%lu:%lu",hour,min,sec);
            }
        }
	}
}

bool GetClientCursorPos(LPPOINT cursor) // translates the cursor so it's relative to the gw window (so we can drag the timer around)
{
	POINT newcur;
	HWND hwnd;
	try {
		GetCursorPos(&newcur);
		EnumWindows((WNDENUMPROC) EnumWindowProc,(LPARAM)&hwnd);
		ScreenToClient(hwnd,&newcur);
		memcpy(cursor,&newcur,sizeof(POINT));
		return true;
	} catch(...) {
		return false;
	}
}

void MouseLoop() // Loop to check if the user is trying to drag the timer around
{
	POINT Cur;
	GetClientCursorPos(&Cur);

	if((Cur.x > transbox.x) && (Cur.x < (transbox.x + transbox.w)) && (Cur.y > transbox.y) && (Cur.y < (transbox.y + transbox.h))) {
		int MouseKey;
		if(GetSystemMetrics(SM_SWAPBUTTON) == 0) { 
			MouseKey = VK_LBUTTON; // Normal
		} else {
			MouseKey = VK_RBUTTON; // Swapped (for left hadned people)
		}
		if(GetAsyncKeyState(MouseKey) < 0) { // Mouse button is down
			int lastx = Cur.x;
			int lasty = Cur.y;
			char tempstr[50];
			while (GetAsyncKeyState(MouseKey) < 0) {
				Sleep(10);
				GetClientCursorPos(&Cur);
				transbox.x += (Cur.x - lastx);
				textbox.x = transbox.x + indentx;
				lastx = Cur.x;
				transbox.y += (Cur.y - lasty);
				textbox.y = transbox.y + indenty;
				lasty = Cur.y;
				sprintf_s(tempstr,"%f",transbox.x);
				WritePrivateProfileStringA("box","x",tempstr,inifile);
				sprintf_s(tempstr,"%f",transbox.y);
				WritePrivateProfileStringA("box","y",tempstr,inifile);
				UpdateText(GetTimeInTheMap());
			}
		}
	}
}

void TextBoxLoop()
{
	while (true) {
		UpdateText(GetTimeInTheMap()); // Update timer to show the correct time
		MouseLoop();  // check if user is trying to drag the timer
		Sleep(100);
	}
}